/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.harmony.javax.security.auth.kerberos;

import java.io.IOException;
import java.io.Serializable;
import java.security.Permission;
import java.security.PermissionCollection;

import org.apache.harmony.auth.internal.nls.Messages;

public final class ServicePermission extends Permission implements Serializable {

	private static final long serialVersionUID = -1227585031618624935L;

	private static final String INITIATE = "initiate"; //$NON-NLS-1$
	private static final String ACCEPT = "accept"; //$NON-NLS-1$
	private static final String INITIATE_ACCEPT = "initiate,accept"; //$NON-NLS-1$
	private static final String[] ACTIONS_TABLE = {
			"", ACCEPT, INITIATE, INITIATE_ACCEPT }; //$NON-NLS-1$

	private final static char ACCEPT_MASK = 1;
	private final static char INITIATE_MASK = 2;

	private static final int INITIATE_LEN = INITIATE.length();
	private static final int ACCEPT_LEN = ACCEPT.length();
	private static final int MIN_LEN = Math.min(INITIATE_LEN, ACCEPT_LEN);

	/**
	 * ACCEPT_MASK, INITIATE_ACCEPT or (INITIATE_ACCEPT | ACCEPT_MASK)
	 */
	private String actions;

	public ServicePermission(String name, String actions) {
		super(name);

		initActions(actions);

		if (name == null) {
			throw new NullPointerException(Messages.getString("auth.2F")); //$NON-NLS-1$
		}
		if (name.trim().length() == 0) {
			throw new IllegalArgumentException(Messages.getString("auth.30")); //$NON-NLS-1$
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}

		if (obj == null || ServicePermission.class != obj.getClass()) {
			return false;
		}
		final ServicePermission sp = (ServicePermission) obj;

		return actions == sp.actions && getName().equals(sp.getName());
	}

	@Override
	public String getActions() {
		return actions;
	}

	@Override
	public int hashCode() {
		return getName().hashCode() * actions.length();
	}

	@Override
	public boolean implies(Permission permission) {
		if (this == permission) {
			return true;
		}

		if (permission == null
				|| ServicePermission.class != permission.getClass()) {
			return false;
		}

		final ServicePermission sp = (ServicePermission) permission;
		final String name = getName();

		return (actions == INITIATE_ACCEPT || actions == sp.actions)
				&& (name.length() == 1 && name.charAt(0) == '*' || name
						.equals(permission.getName()));
	}

	// initialization of actions
	private void initActions(String actions) {
		if (actions == null || actions.length() < MIN_LEN) {
			throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
		}

		final char[] c_acts = actions.toCharArray();

		int result = 0;
		int ptr = 0;

		final int len6 = c_acts.length - ACCEPT_LEN;
		final int len8 = c_acts.length - INITIATE_LEN;

		do {
			// skipping whitespaces
			while (ptr <= len6
					&& (c_acts[ptr] == ' ' || c_acts[ptr] == '\t'
							|| c_acts[ptr] == '\n' || c_acts[ptr] == 0x0B
							|| c_acts[ptr] == '\f' || c_acts[ptr] == '\r')) {
				++ptr;
			}

			if (ptr > len6) {
				// expect string "accept" or "initiate", not just white
				// spaces
				throw new IllegalArgumentException(
						Messages.getString("auth.2E")); //$NON-NLS-1$
			}

			// parsing string
			if ((c_acts[ptr] == 'a' || c_acts[ptr] == 'A')
					&& (c_acts[ptr + 1] == 'c' || c_acts[ptr + 1] == 'C')
					&& (c_acts[ptr + 2] == 'c' || c_acts[ptr + 2] == 'C')
					&& (c_acts[ptr + 3] == 'e' || c_acts[ptr + 3] == 'E')
					&& (c_acts[ptr + 4] == 'p' || c_acts[ptr + 4] == 'P')
					&& (c_acts[ptr + 5] == 't' || c_acts[ptr + 5] == 'T')) {
				result |= ACCEPT_MASK;
				ptr += ACCEPT_LEN;
			} else if (ptr <= len8
					&& (c_acts[ptr] == 'i' || c_acts[ptr] == 'I')
					&& (c_acts[ptr + 1] == 'n' || c_acts[ptr + 1] == 'N')
					&& (c_acts[ptr + 2] == 'i' || c_acts[ptr + 2] == 'I')
					&& (c_acts[ptr + 3] == 't' || c_acts[ptr + 3] == 'T')
					&& (c_acts[ptr + 4] == 'i' || c_acts[ptr + 4] == 'I')
					&& (c_acts[ptr + 5] == 'a' || c_acts[ptr + 5] == 'A')
					&& (c_acts[ptr + 6] == 't' || c_acts[ptr + 6] == 'T')
					&& (c_acts[ptr + 7] == 'e' || c_acts[ptr + 7] == 'E')) {
				result |= INITIATE_MASK;
				ptr += INITIATE_LEN;
			} else {
				throw new IllegalArgumentException(
						Messages.getString("auth.2E")); //$NON-NLS-1$
			}

			// skipping trailing whitespaces
			while (ptr < c_acts.length
					&& (c_acts[ptr] == ' ' || c_acts[ptr] == '\t'
							|| c_acts[ptr] == '\n' || c_acts[ptr] == 0x0B
							|| c_acts[ptr] == '\f' || c_acts[ptr] == '\r')) {
				ptr++;
			}

			if (ptr == c_acts.length) {
				this.actions = ACTIONS_TABLE[result];
				return;
			}
		} while (c_acts[ptr++] == ',');

		// unknown trailing symbol
		throw new IllegalArgumentException(Messages.getString("auth.2E")); //$NON-NLS-1$
	}

	@Override
	public PermissionCollection newPermissionCollection() {
		return new KrbServicePermissionCollection();
	}

	private synchronized void readObject(java.io.ObjectInputStream s)
			throws IOException, ClassNotFoundException {
		s.defaultReadObject();
		initActions(getActions());
	}

	private synchronized void writeObject(java.io.ObjectOutputStream s)
			throws IOException {
		s.defaultWriteObject();
	}
}
